﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Xenta.Utils;

namespace Xenta
{
    /// <summary>
    /// Exposes the interface of a pool of units of work.
    /// </summary>
    public interface IUnitOfWorkPool : IDisposable
    {
        /// <summary>
        /// Leases the unit of work from the pool by guid.
        /// </summary>
        /// <param name="guid">The global unique identifier.</param>
        /// <returns>The unit of work leasehold.</returns>
        UnitOfWorkLeasehold Lease(Guid guid);

        /// <summary>
        /// Returns the previously leased unit of work to the pool.
        /// </summary>
        /// <param name="leasehold">The unit of work leasehold.</param>
        void Return(UnitOfWorkLeasehold leasehold);
    }

    /// <summary>
    /// Represents the unit of work of work leasehold.
    /// </summary>
    public class UnitOfWorkLeasehold : IDisposable
    {
        #region Fields

        private IUnitOfWork _uow;
        private readonly Guid _guid;

        #endregion

        #region Ctors

        /// <summary>
        /// Initializes a new instance of the class.
        /// </summary>
        /// <param name="uow">The unit of work.</param>
        /// <param name="guid">The global unique identifier.</param>
        public UnitOfWorkLeasehold(IUnitOfWork uow, Guid guid)
        {
            if(uow == null)
                throw new ArgumentNullException("uow");
            _uow = uow;
            _guid = guid;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets the unit of work.
        /// </summary>
        public IUnitOfWork UoW
        {
            get
            {
                return _uow;
            }
        }

        /// <summary>
        /// Gets the global unique identifier.
        /// </summary>
        public Guid Guid
        {
            get
            {
                return _guid;
            }
        }

        /// <summary>
        /// Gets a value indicating whether the leasehold is active.
        /// </summary>
        internal bool IsActive
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the leashold expiration time.
        /// </summary>
        internal DateTime ExpiredOn
        {
            get;
            set;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Dispose the leasehold and associated unit of work.
        /// </summary>
        public void Dispose()
        {
            if(_uow == null) 
                return;
            _uow.Dispose();
            _uow = null;
        }

        #endregion
    }

    /// <summary>
    /// The IUnitOfWorkPool implementation.
    /// </summary>
    public class UnitOfWorkPoolImpl : IUnitOfWorkPool
    {
        #region Constants

        /// <summary>
        /// Defines the unit of work lease time.
        /// </summary>
        /// <remarks>
        /// This pool implementation has some kind of a watchdog 
        /// timer. The timer triggers with LEASE_TIME / 2 period. 
        /// If timer function finds the leasehold as expired and 
        /// inactive, it will dispose the leasehold.
        /// </remarks>
        public const int LEASE_TIME = 10 * 1000;

        #endregion

        #region Fields

        private readonly IUnitOfWorkFactory _factory;
        private readonly List<UnitOfWorkLeasehold> _pool;
        private readonly object _lock;
        private readonly Timer _cleanTimer;

        #endregion

        #region Ctors

        /// <summary>
        /// Initializes a new instance of the class.
        /// </summary>
        /// <param name="factory">The unit of work factory.</param>
        public UnitOfWorkPoolImpl(IUnitOfWorkFactory factory)
        {
            _factory = factory;
            _pool = new List<UnitOfWorkLeasehold>();
            _lock = new Object();
            _cleanTimer = new Timer(Release, null, LEASE_TIME / 2, LEASE_TIME / 2);
        }

        #endregion

        #region Methods

        /// <summary>
        /// Leases the unit of work from the pool by guid.
        /// </summary>
        /// <param name="guid">The global unique identifier.</param>
        /// <returns>The unit of work leasehold.</returns>
        public UnitOfWorkLeasehold Lease(Guid guid)
        {
            UnitOfWorkLeasehold leasehold = null;

            lock(_lock)
            {
                leasehold = _pool.SingleOrDefault(x => x.Guid.Equals(guid));

                if(leasehold == null)
                {
                    var uow = _factory.Produce();
                    leasehold = new UnitOfWorkLeasehold(uow, guid);

                    _pool.Add(leasehold);
                }
                else if(leasehold.IsActive)
                {
                    throw Err.Of(this).Message("UoW is already leased").Ex();
                }
                leasehold.ExpiredOn = DateTime.UtcNow.AddMilliseconds(LEASE_TIME);
                leasehold.IsActive = true;
            }
            return leasehold;
        }

        /// <summary>
        /// Returns the previously leased unit of work to the pool.
        /// </summary>
        /// <param name="leasehold">The unit of work leasehold.</param>
        public void Return(UnitOfWorkLeasehold leasehold)
        {
            if(leasehold == null)
                return;
            lock(_lock)
            {
                if(!_pool.Contains(leasehold))
                    throw Err.Of(this).Message("UoW is not leased").Ex();
                if(!leasehold.IsActive)
                    return;

                leasehold.IsActive = false;
                leasehold.UoW.Suspend();
            }
        }

        /// <summary>
        /// Stops the timers. Disposes all the leasehold. 
        /// Disposes the UoW factory.
        /// </summary>
        public void Dispose()
        {
            lock(_lock)
            {
                _cleanTimer.Dispose();
                foreach(var leasehold in _pool)
                    leasehold.Dispose();
                _pool.Clear();
                _factory.Dispose();
            }
        }

        #endregion

        #region Utilities

        private void Release(object state)
        {
            bool idleRun = true;

            lock(_lock)
            {
                if(_pool.Any())
                {
                    var dtNow = DateTime.UtcNow;
                    var tmp = _pool.Where(x => !x.IsActive &&
                        x.ExpiredOn.CompareTo(dtNow) <= 0);
                    idleRun = !tmp.Any();

                    if(!idleRun)
                    {
                        foreach(var i in tmp.ToList())
                        {
                            i.Dispose();
                            _pool.Remove(i);
                        }
                    }
                }
            }
            if(!idleRun)
                GC.Collect();
        }

        #endregion
    }
}
